package org.hertsig.legality;

import static com.google.common.collect.Iterables.filter;
import static com.google.common.collect.Iterables.transform;

import java.io.IOException;
import java.io.InputStreamReader;
import java.io.Reader;
import java.util.List;
import java.util.Set;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.Setter;
import lombok.SneakyThrows;

import org.hertsig.entities.Deck;

import com.google.common.base.Function;
import com.google.common.base.Predicates;
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Multiset;
import com.google.common.io.CharSource;

public class LegalityChecker {
	public static final LegalityChecker COMMANDER = new CommanderLegalityChecker();
	public static final LegalityChecker LEGACY = new LegalityChecker("Legacy", parse("banlist/legacy-banned.txt"), ImmutableSet.<String>of());
	public static final LegalityChecker VINTAGE = new LegalityChecker("Vintage", parse("banlist/vintage-banned.txt"), parse("banlist/vintage-restricted.txt"));
	public static final LegalityChecker LEGACY_OR_VINTAGE = new CombinedLegalityChecker("Legacy/Vintage", LEGACY, VINTAGE);
	
	public static final Set<String> BASICS = ImmutableSet.of("Plains", "Island", "Swamp", "Mountain", "Forest",
			"Snow-covered plains", "Snow-Covered Island", "Snow-Covered Swamp", "Snow-Covered Mountain", "Snow-Covered Forest");
	
	private final String format;
	private final ImmutableSet<String> banned;
	private final ImmutableSet<String> restricted;
	
	@Setter private boolean checkSideboard = false;
	@Setter private int maxPerCard = 4;
	@Setter private int minDeckSize = 60;
	
	@SneakyThrows(IOException.class)
	protected static Set<String> parse(final String fileName) {
		List<String> lines = new CharSource() {
			@Override
			public Reader openStream() throws IOException {
				return new InputStreamReader(getClass().getClassLoader().getResource(fileName).openStream());
			}
		}.readLines();
		return ImmutableSet.copyOf(filter(transform(lines, new Function<String, String>() {
			@Override
			public String apply(String input) {
				return Strings.emptyToNull(Strings.nullToEmpty(input).trim());
			}
		}), Predicates.notNull()));
	}
	
	protected LegalityChecker(String format, Set<String> banned, Set<String> restricted) {
		this.format = format;
		this.banned = ImmutableSet.copyOf(banned);
		this.restricted = ImmutableSet.copyOf(restricted);
	}
	
	public ImmutableSet<Conflict> check(Deck deck) {
		ImmutableSet.Builder<Conflict> builder = ImmutableSet.builder();
		check(deck.getMainDeck(), builder);
		if (deck.getMainDeck().size() < minDeckSize) {
			builder.add(new Conflict(Conflict.Type.AMOUNT, "At least " + minDeckSize + " cards in the deck", format));
		}
		if (checkSideboard) {
			check(deck.getSideboard(), builder);
		}
		return builder.build();
	}
	
	protected void check(Multiset<String> cards, ImmutableSet.Builder<Conflict> builder) {
		for (String card : cards.elementSet()) {
			int count = cards.count(card);
			if (banned.contains(card)) {
				builder.add(new Conflict(Conflict.Type.BANNED, card, format));
			}
			else if (restricted.contains(card) && count > 1) {
				builder.add(new Conflict(Conflict.Type.RESTRICTED, card, format));
			}
			else if (count > maxPerCard && !BASICS.contains(card)) {
				builder.add(new Conflict(Conflict.Type.AMOUNT, card, format));
			}
		}
	}
	
	@Data @AllArgsConstructor @EqualsAndHashCode(exclude="format")
	public static class Conflict {
		public enum Type { BANNED, RESTRICTED, AMOUNT, OTHER }
		
		private final Type type;
		private final String message;
		private final String format;
	}
}
